package com.fyb.doublecache.service;


import com.fyb.doublecache.common.Constants;
import com.fyb.doublecache.entity.Algorithminfo;
import com.fyb.doublecache.mapper.AlgoritmMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @Author：MR.FANG
 * @name：AlgorithmService
 * @Date：2025/7/3 上午10:18
 * 算法信息服务类
 * 实现了算法的查询和更新功能，使用延迟双删方案保证数据一致性
 */
@Service
public class AlgorithmService {

    @Autowired
    private AlgoritmMapper algorithmMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private CacheManager layeredCacheManager;

    @Value("${cache.delete.delay:1000}")
    private long cacheDeleteDelay;


    /**
     * 获取算法信息
     * 使用@Cacheale注解实现缓存，优先从缓存获取数据
     * 缓存未命中时从数据库查询并自动缓存结果
     * @return
     */
    @Cacheable(value = "algorithm",key = "#id",cacheManager = "layeredCacheManager")
    public Algorithminfo getAlgorithm(String id){
        return algorithmMapper.findById(id);
    }

    /**
     * 更新算法信息
     * 使用延迟双删方案保证数据一致性：
     * 1.获取分布式锁
     * 2.先删除缓存（包括本地缓存和Redis缓存）
     * 3.更新数据库
     * 4.延迟一段时间后再次删除缓存
     * 5.释放分布式锁
     */
    @Transactional
    public void updateAlgorithm(Algorithminfo info){
        String lockKey = Constants.LOCK_PREFIX  + info.getId();
        String lockValue = String.valueOf(System.currentTimeMillis());

        try {
            // 1. 获取分布式锁
            boolean locked = acquireLock(lockKey, lockValue);
            if (!locked) {
                throw new RuntimeException("获取分布式锁失败");
            }

            String cacheKey = info.getId();

            // 2. 先删除缓存
            deleteCache(cacheKey);

            // 3. 更新数据库
            int updated = algorithmMapper.update(info);
            if (updated == 0) {
                throw new RuntimeException("更新数据库失败");
            }

            // 4. 延迟删除缓存（异步执行）
            delayDeleteCache(cacheKey);

        } catch (Exception e) {
            // 发生异常时回滚事务
            throw new RuntimeException("更新算法信息失败", e);
        } finally {
            // 5. 释放分布式锁
            releaseLock(lockKey, lockValue);
        }
    }

    /**
     * 获取分布式锁
     * @param lockKey 锁的键
     * @param lockValue 锁的值
     * @return 是否获取成功
     */
    private boolean acquireLock(String lockKey, String lockValue) {
        Long result = redisTemplate.execute(
                Constants.LOCK_SCRIPT,
                Collections.singletonList(lockKey),
                lockValue,
                Constants.LOCK_TIMEOUT
        );
        return result != null && result == 1;
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的键
     * @param lockValue 锁的值
     */
    private void releaseLock(String lockKey, String lockValue) {
        redisTemplate.execute(
                Constants.UNLOCK_SCRIPT,
                Collections.singletonList(lockKey),
                lockValue
        );
    }

    /**
     * 删除缓存
     * 同时删除本地缓存和Redis缓存
     * @param cacheKey 缓存键
     */
    private void deleteCache(String cacheKey) {
        try {
            // 删除本地缓存
            Cache cache = layeredCacheManager.getCache("algorithm");
            if (cache != null) {
                cache.evict(cacheKey);
            }

            // 删除Redis缓存
            String redisKey = "algorithm::" + cacheKey;
            redisTemplate.delete(redisKey);
        } catch (Exception e) {
            throw new RuntimeException("删除缓存失败", e);
        }
    }

    /**
     * 延迟删除缓存
     * 使用@Async注解实现异步执行
     * @param cacheKey 缓存键
     */
    @Async
    public void delayDeleteCache(String cacheKey) {
        try {
            // 延迟指定时间后再次删除缓存
            TimeUnit.MILLISECONDS.sleep(cacheDeleteDelay);
            deleteCache(cacheKey);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("延迟删除缓存被中断", e);
        }
    }
}
